/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package ua.yandex.collections;

/**
 *
 * @author vindex10
 */
public final class MyArrayList implements MyList {
  static final int MAXLENGTH = Integer.MAX_VALUE;
  
  private Object[] data;
  private int capacity;
  
  public MyArrayList(int initial) {
    capacity = 0;
    data = new Object[initial];
  }
  
  public MyArrayList() {
    this(10);
  }
  
  public MyArrayList(final Object ... in) {
    capacity = in.length;
    data = new Object[in.length];
    System.arraycopy(in, 0, data, 0, in.length);
  }
  
  @Override
  public void add(final Object in) {
    multiAdd(getCap(), in);
  }
  
  @Override
  public void add(final int index, final Object in) {
    multiAdd(index, in);
  }
  
  @Override
  public void addAll(final Object[] in) {
    multiAdd(getCap(), in);
  }
  
  @Override
  public void addAll(final int index, final Object[] in) {
    multiAdd(index, in);
  }
  
  @Override
  public Object get(final int index) {
    getPrecheck(index);
    return data[index];
  }
  private void getPrecheck(final int index) {
    if (!inList(index)) {
      throw new ArrayIndexOutOfBoundsException();
    }
  }
  
  @Override
  public Object remove(final int index) {
    Object res = get(index); //get() throws exception if out of range
    shiftData(index+1, -1);
    data[getCap() - 1] = null;
    capacity--;
    return res;
  }
  
  @Override
  public void set(final int index, final Object e) {
    setPrecheck(index, e);
    data[index] = e;
  }
  private void setPrecheck(final int index, final Object e) {
    if (!inList(index)) {
      throw new ArrayIndexOutOfBoundsException();
    }
  }
  
  @Override
  public int indexOf(Object o) {
    int i = 0;
    while (i < getCap() && !o.equals(data[i])) {
      i++;
    }
    if (i == getCap()) {
      return -1;
    } else {
      return i;
    }
  }
  
  @Override
  public int size() {
    return getCap();
  }
  
  @Override
  public void clear() {
    capacity = 0;
    freeMem();
  }
  
  @Override
  public boolean isEmpty() {
    return getCap() == 0;
  }
  
  @Override
  public Object[] toArray() {
    Object[] res = new Object[getCap()];
    System.arraycopy(data, 0, res, 0, getCap());
    return res;
  }
  
  @Override
  public String toString() {
    StringBuilder res = new StringBuilder("");
    if (isEmpty()) {
      return res.toString();
    }
    for (int i = 0; i < getCap(); i++) {
      res.append(data[i].toString()).append(", ");
    }
    return res.substring(0, res.length() - 2);
  }
  
  public void ensureCapacity(int minCap) {
    int increment = minCap - getCap() + 1;
    scaleData(increment);
  }
  
  public void freeMem() {
    int delta = getLength() - getCap();
    if (delta > getCap()) {
      int newLen = 2*getCap();
      Object[] newData = new Object[newLen];
      System.arraycopy(data, 0, newData, 0, capacity);
      data = newData;
    }
  }
  
  private void multiAdd(final int index, final Object ... in) {
    multiAddPrecheck(index, in);
    
    scaleData(in.length);
    if (inList(index)) {
      shiftData(index, in.length);
    }
    System.arraycopy(in, 0, data, index, in.length);
    capacity += in.length;
  }
  
  private void multiAddPrecheck(final int index, final Object[] in) {
    if (!inList(index) && !isNextAfterLast(index)) {
      throw new ArrayIndexOutOfBoundsException();
    }
  }
  
  private void scaleData(final int increment) {
    // we dont need precheck, Integer[MAXINT] takes 2GB, we'll get OutOfMemory

    int delta = getCap() - getLength() + increment;
    if (delta > 0) {
      int newLen = calcNewLength(delta);
      Object[] newData = new Object[newLen];
      System.arraycopy(data, 0, newData, 0, capacity);
      data = newData;
    }
  }
  
  private int calcNewLength(final int delta) {
    //suppose new length lower then MAXLENGTH, and delta > 0
    int res = MAXLENGTH;
    if (delta < getCap()) { //if delta < cap => l += cap
      if (getLength() <= MAXLENGTH - getCap()) {
        return getLength() + getCap();
      }
    } else { //else find min k so that 2^k * l > l + delta
      int newlen = getLength();
      boolean lowerThenMax = false;
      while (Math.log(newlen) + Math.log(2) < Math.log(MAXLENGTH)) {
        newlen = newlen*2;
        if (newlen - getLength() > delta) {
          lowerThenMax = true;
          break;
        }
      }
      if (lowerThenMax) {
        return newlen;
      }
    }
    return MAXLENGTH; //if in all cases res > MAXLENGTH, return MAXLENGTH
  }
  
  
  //Short scripts
  
  private int getLength() {
    return data.length;
  }
  private int getCap() {
    return capacity;
  }
  private void shiftData(int start, int shift) {
    int newlen = data.length;
    if (shift < 0) {
      newlen -= shift;
    }
    Object[] newData = new Object[newlen];
    System.arraycopy(data, 0, newData, 0, start);
    System.arraycopy(data, start, newData, start + shift, getCap() - start);
    data = newData;
  }
  private boolean inList(int index) {
    return (index >= 0 && index < getCap());
  }
  private boolean isNextAfterLast(int index) {
    return (index == getCap());
  }
  public boolean touchCell(int index) {
    return data.length > index;
  }
}